package wangwenjun.phase3.atomic;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import util.ThreadUtil;

import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

/**
 * @author ChangLiang
 * @date 2020/6/20
 */
@Slf4j
public class AtomicReferenceTest {


    @Test
    @DisplayName("test basic")
    public void test() {
        ProductInventory productInventory = new ProductInventory(1, 100);
        AtomicReference<ProductInventory> atomicReference = new AtomicReference<>(productInventory);
        // 相等的比较算法是 == 即基本数据类型判断值，对象类型判断引用地址是否相同
        boolean b = atomicReference.compareAndSet(productInventory, new ProductInventory(productInventory.getProductId(), productInventory.getInventoryNum() - 1));
        assertThat(b).isTrue();
    }

    @Test
    @DisplayName("test seq thread")
    public void test2() throws InterruptedException {
        ProductInventory productInventory = new ProductInventory(1, 100);
        AtomicReference<ProductInventory> atomicReference = new AtomicReference<>(productInventory);

        //(1,100)->(1,99)
        reduceProductInventory(productInventory, atomicReference);

        // (1,99)->(1,98)
        Thread thread02 = new Thread(() -> {
            reduceProductInventory(productInventory, atomicReference);
        }, "set-product-inventory-thread-2");
        thread02.start();
        thread02.join();

        log.info("Thread:{} inventory num:{}", Thread.currentThread(), atomicReference.get().getInventoryNum());
    }

    @Test
    @DisplayName("test concurrency")
    public void test3() throws InterruptedException {

        // 共享资源
        ProductInventory productInventory = new ProductInventory(1, 100);
        AtomicReference<ProductInventory> atomicReference = new AtomicReference<>(productInventory);

        // (1,100)->(1,99)
        Thread thread01 = new Thread(() -> {
            reduceProductInventory(productInventory, atomicReference);
        }
                , "set-product-inventory-thread-1");

        // (1,100)->(1,99)
        Thread thread02 = new Thread(() -> {
            reduceProductInventory(productInventory, atomicReference);
        }
                , "set-product-inventory-thread-2");
        thread01.start();
        thread02.start();
        thread01.join();
        thread02.join();
        log.info("Thread:{} inventory num:{}", Thread.currentThread(), atomicReference.get().getInventoryNum());
    }

    @Test
    @DisplayName("test cas")
    public void test4() throws InterruptedException {
        ProductInventory productInventory = new ProductInventory(1, 5);
        AtomicReference<ProductInventory> atomicReference = new AtomicReference<>(productInventory);
        IntStream.range(0, 10).forEach((i) -> new Thread(() -> {
            try {
                reduceProductInventory(productInventory, atomicReference);
            } catch (Exception e) {
                log.error(ThreadUtil.exceptionToString(e));
            }
        },
                "product-inventory-reduce-thread-" + i
        ).start());
        Thread.currentThread().join();
    }

    private void reduceProductInventory(ProductInventory productInventory, AtomicReference<ProductInventory> atomicReference) {
        boolean result = false;
        while(!result) {
            if (productInventory.getInventoryNum() <= 0) {
                throw new IllegalArgumentException("all the product has been sold out");
            }
            ProductInventory newProductInventory = new ProductInventory(productInventory.getProductId(), productInventory.getInventoryNum() - 1);
            ThreadUtil.sleepForMilliSeconds(3_000);
            result = atomicReference.compareAndSet(productInventory, newProductInventory);
            Integer tempNum = productInventory.getInventoryNum();
            if (!result) {
                productInventory = atomicReference.get();
            }
            log.warn("reduce product inventory, result:{}  num from:{} to:{}",
                    result,
                    tempNum,
                    newProductInventory.getInventoryNum());
        }
    }

    @Test
    @DisplayName("test synchronized")
    public void test5() throws InterruptedException {
        ProductInventory productInventory = new ProductInventory(1, 5);
        IntStream.range(0, 10).forEach((i) -> new Thread(() -> {
            try {
                synchronizedReduceProductInventory(productInventory);
            } catch (Exception e) {
                log.error(ThreadUtil.exceptionToString(e));
            }
        },
                "product-inventory-reduce-thread-" + i
        ).start());
        Thread.currentThread().join();
    }

    private synchronized void synchronizedReduceProductInventory(ProductInventory productInventory) {
        if (productInventory.getInventoryNum() <= 0) {
            throw new IllegalArgumentException("all the product has been sold out");
        }
        ThreadUtil.sleepForMilliSeconds(3_000);
        productInventory.setInventoryNum(productInventory.getInventoryNum() - 1);
        log.warn("Thread:{} reduce product inventory, productInventory:{}",
                Thread.currentThread(),
                productInventory);
    }
}
